package br.unifor.cct.mia.coevolution.evaluate;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import br.unifor.cct.mia.coevolution.InvalidTypeException;
import br.unifor.cct.mia.coevolution.memory.MemoryBuffer;
import br.unifor.cct.mia.coevolution.memory.MemoryIndividual;
import br.unifor.cct.mia.coevolution.memory.SharedMemory;
import br.unifor.cct.mia.dataenhancement.Database;
import br.unifor.cct.mia.dataenhancement.Structure;
import br.unifor.cct.mia.dataenhancement.Tuple;
import br.unifor.cct.mia.evaluate.classification.WekaClassification;
import br.unifor.cct.mia.evolutionary.FeatureConstructionGenotype;
import br.unifor.cct.mia.evolutionary.Genotype;
import br.unifor.cct.mia.evolutionary.Individual;
import br.unifor.cct.mia.evolutionary.Particula;
import br.unifor.cct.mia.evolutionary.SpeciesConstants;
import br.unifor.cct.mia.util.Methods;

import com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable;

public class MemoryBasedEvaluation {
	
	private static Hashtable evaluated = new Hashtable();

		
	public static synchronized double evaluate(Individual ind, int specieType, MemoryBuffer atualMemory,
			Structure struc, Database db, Tuple tuples, List positions, 
			Integer leanerType,String[] options, int nBits) throws IOException, InterruptedException, InvalidTypeException {
		
		double result = 0;	
		String fileName = "temp/"+"resultCoev"+specieType+"-"+new Date().getTime()+".txt";
		
		File resultFile = new File(fileName);
		resultFile.delete();		
		resultFile = null;
				
		List buffer = atualMemory.getMemoryElements();
		
		synchronized (MemoryBuffer.BUFFER_SEMAPHORE) {
			
			//Para cada individuo do buffer deve rodar
			if ( buffer.size() == 0 ) {
				SharedMemory memoryAux = new SharedMemory();		
				MemoryIndividual mInd = new MemoryIndividual(ind,specieType,nBits);
				memoryAux.add(mInd);

				resultFile = EvaluateUtils.memoryToFile(memoryAux,fileName,struc,db,tuples,positions,nBits);					
				WekaClassification classificator = new WekaClassification(leanerType,options);
				result = classificator.evaluate(resultFile);	

				ind.setFitness(result);
				memoryAux.setFitness(result);
				
				resultFile.delete();	
				
				atualMemory.add(memoryAux);	
			}
			else {
				double fitness[] = new double[buffer.size()];
				SharedMemory memorys[] = new SharedMemory[buffer.size()];
				
				for ( int i=0; i<buffer.size(); i++ ) {
					
					SharedMemory memoryAux = new SharedMemory();		
					MemoryIndividual mInd = new MemoryIndividual(ind,specieType,nBits);
					memoryAux.add(mInd);
					
					SharedMemory memoryElement = (SharedMemory)buffer.get(i);
					
					ArrayList list = memoryElement.getElements();
					for (Iterator iter = list.iterator(); iter.hasNext();) {
						MemoryIndividual element = (MemoryIndividual) iter.next();
						
						if ( element.getSpecieType() != specieType ) {
							memoryAux.add(element);
						}
					}
					
					GroupKey key = createGroupKey(memoryAux.getElements(),ind,specieType);
					if ( evaluated.containsKey(key) ) {						
						result = ((Double)evaluated.get(key)).doubleValue();	
					}
					else {
						if ( specieType == SpeciesConstants.MISSING_VALUE )
							resultFile = EvaluateUtils.memoryToFile(memoryAux,fileName,struc,db,tuples,positions,nBits);
						else
							resultFile = EvaluateUtils.memoryToFile(memoryAux,fileName,struc,db,tuples,positions,nBits);
						
						WekaClassification classificator = new WekaClassification(leanerType,options);
						result = classificator.evaluate(resultFile);	
						
						ind.setFitness(result);						
						resultFile.delete();

						evaluated.put(key,new Double(result));
					}
					
					memoryAux.setFitness(result);
					fitness[i] = memoryAux.getFitness();
					memorys[i] = memoryAux;										
				}				
				
				int pos = Methods.arrayMaximunPos(fitness);
				SharedMemory bestMemory = memorys[pos];
				
				atualMemory.add(bestMemory);					
			}
			
		}
		
		
		return result;
	}

	private static GroupKey createGroupKey(List atualMemory, Individual ind, int specie) {		
		GroupKey gk = new GroupKey();
		
		for ( int i=0; i<atualMemory.size(); i++ ) {
			MemoryIndividual memIndividual = (MemoryIndividual)atualMemory.get(i);
			
			if ( memIndividual.getSpecieType() != specie ) {
				HashKey k = new HashKey();
				k.setSpecie( memIndividual.getSpecieType() );
				if ( memIndividual.getIndividual() instanceof Genotype )
					k.setHashCode( ((Genotype)memIndividual.getIndividual()).hashCode() );
				else if ( memIndividual.getIndividual() instanceof FeatureConstructionGenotype )
					k.setHashCode( ((FeatureConstructionGenotype)memIndividual.getIndividual()).hashCode() );
				else if ( memIndividual.getIndividual() instanceof Particula )
					k.setHashCode( ((Particula)memIndividual.getIndividual()).hashCode() );
				gk.add(k);
			}
		}
		
		HashKey k = new HashKey();
		k.setSpecie( specie );
		if ( ind instanceof Genotype )
			k.setHashCode( ((Genotype)ind).hashCode() );
		else if ( ind instanceof FeatureConstructionGenotype )
			k.setHashCode( ((FeatureConstructionGenotype)ind).hashCode() );		
		else if ( ind instanceof Particula )
			k.setHashCode( ((Particula)ind).hashCode() );
		gk.add(k);
		
		return gk;
	}
	
	static class HashKey {
		int specie;
		int hash;
		
		public void setSpecie(int i) { specie = i; }		
		public void setHashCode(int i) { hash = i; }
		
		public boolean equals(Object obj) {
			HashKey key = (HashKey)obj;
			
			if ( specie == key.specie &&
					hash == key.hash ) {
				return true;
			} else {			
				return false;
			}
		}
		
		public int hashCode() {			
			return specie+hash;
		}		
	}
	
	static class GroupKey {
		List keys = new ArrayList();
		
		public void add(HashKey k) {
			keys.add(k);
		}
		
		public boolean equals(Object obj) {			
			GroupKey gk = (GroupKey)obj;
			
			if ( gk.keys.size() != keys.size() ) {
				return false;
			} else {
				Iterator iter = keys.iterator();
				while ( iter.hasNext() ) {
					HashKey key = (HashKey)iter.next();
					if ( !gk.keys.contains(key) ) {
						return false;
					}
				}
			}
			return true;			
		}
		
		public int hashCode() {
			Iterator iter = keys.iterator();
			int hash = 0;
			while ( iter.hasNext() ) {
				HashKey key = (HashKey)iter.next();
				hash += key.hash;
			}
			return hash;
		}
	}
	
}
